// Copyright Epic Games, Inc. All Rights Reserved.

#include "LyraWeaponInstance.h"

#include "GameFramework/Pawn.h"
#include "Engine/World.h"
#include "Math/UnrealMathUtility.h"
#include "Misc/AssertionMacros.h"
#include "GameFramework/InputDeviceSubsystem.h"
#include "GameFramework/InputDeviceProperties.h"
#include "Character/LyraHealthComponent.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraWeaponInstance)

class UAnimInstance;
struct FGameplayTagContainer;

/**
 * ULyraWeaponInstance 构造函数。
 * @param ObjectInitializer 对象初始化器
 */
ULyraWeaponInstance::ULyraWeaponInstance(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	// 监听拥有Pawn的死亡，以便在死亡且无法卸下时移除任何设备属性
	if (APawn* Pawn = GetPawn())
	{
		// 我们只需要为玩家控制的Pawn执行此操作，因为AI和其他人在客户端上没有输入设备
		if (Pawn->IsPlayerControlled())
		{
			// 查找健康组件并绑定死亡事件
			if (ULyraHealthComponent* HealthComponent = ULyraHealthComponent::FindHealthComponent(GetPawn()))
			{
				HealthComponent->OnDeathStarted.AddDynamic(this, &ThisClass::OnDeathStarted);
			}
		}
	}
}

/**
 * 装备武器时调用。
 */
void ULyraWeaponInstance::OnEquipped()
{
	Super::OnEquipped();

	// 记录装备时间
	UWorld* World = GetWorld();
	check(World);
	TimeLastEquipped = World->GetTimeSeconds();

	// 应用设备属性
	ApplyDeviceProperties();
}

/**
 * 卸下武器时调用。
 */
void ULyraWeaponInstance::OnUnequipped()
{
	Super::OnUnequipped();

	// 移除设备属性
	RemoveDeviceProperties();
}

/**
 * 更新开火时间。
 */
void ULyraWeaponInstance::UpdateFiringTime()
{
	UWorld* World = GetWorld();
	check(World);
	TimeLastFired = World->GetTimeSeconds();
}

/**
 * 返回自上次与武器交互（开火或装备）以来经过的时间。
 * @return 返回经过的时间（秒）
 */
float ULyraWeaponInstance::GetTimeSinceLastInteractedWith() const
{
	UWorld* World = GetWorld();
	check(World);
	const double WorldTime = World->GetTimeSeconds();

	// 计算自装备以来的时间
	double Result = WorldTime - TimeLastEquipped;

	// 如果开火过，计算自开火以来的时间，取较小值
	if (TimeLastFired > 0.0)
	{
		const double TimeSinceFired = WorldTime - TimeLastFired;
		Result = FMath::Min(Result, TimeSinceFired);
	}

	return Result;
}

/**
 * 根据指定的游戏标签选择最佳动画层。
 * @param bEquipped 是否装备
 * @param CosmeticTags 装饰性标签容器
 * @return 返回动画实例类
 */
TSubclassOf<UAnimInstance> ULyraWeaponInstance::PickBestAnimLayer(bool bEquipped, const FGameplayTagContainer& CosmeticTags) const
{
	// 根据装备状态选择要查询的动画集
	const FLyraAnimLayerSelectionSet& SetToQuery = (bEquipped ? EquippedAnimSet : UneuippedAnimSet);
	// 选择最佳层
	return SetToQuery.SelectBestLayer(CosmeticTags);
}

/**
 * 返回拥有此武器的Pawn的平台用户ID。
 * @return 返回平台用户ID
 */
const FPlatformUserId ULyraWeaponInstance::GetOwningUserId() const
{
	if (const APawn* Pawn = GetPawn())
	{
		return Pawn->GetPlatformUserId();
	}
	// 如果没有Pawn，返回无效用户ID
	return PLATFORMUSERID_NONE;
}

/**
 * 应用设备属性到此武器的拥有Pawn。
 */
void ULyraWeaponInstance::ApplyDeviceProperties()
{
	// 获取拥有用户ID
	const FPlatformUserId UserId = GetOwningUserId();

	// 检查用户ID是否有效
	if (UserId.IsValid())
	{
		// 获取输入设备子系统
		if (UInputDeviceSubsystem* InputDeviceSubsystem = UInputDeviceSubsystem::Get())
		{
			// 遍历所有适用的设备属性
			for (TObjectPtr<UInputDeviceProperty>& DeviceProp : ApplicableDeviceProperties)
			{
				// 设置激活参数
				FActivateDevicePropertyParams Params = {};
				Params.UserId = UserId;

				// 默认情况下，设备属性将在平台用户的主要输入设备上播放。
				// 如果要覆盖此设置并设置特定设备，可以设置DeviceId参数。
				//Params.DeviceId = <某个特定设备id>;
				
				// 不要在此属性评估后移除它。我们希望属性在持有武器期间持续应用，
				// 并将在OnUnequipped中手动移除它们
				Params.bLooping = true;
			
				// 激活设备属性并存储句柄
				DevicePropertyHandles.Emplace(InputDeviceSubsystem->ActivateDeviceProperty(DeviceProp, Params));
			}
		}	
	}
}

/**
 * 移除在ApplyDeviceProperties中激活的任何设备属性。
 */
void ULyraWeaponInstance::RemoveDeviceProperties()
{
	// 获取拥有用户ID
	const FPlatformUserId UserId = GetOwningUserId();
	
	// 检查用户ID是否有效且设备属性句柄不为空
	if (UserId.IsValid() && !DevicePropertyHandles.IsEmpty())
	{
		// 移除任何已应用的设备属性
		if (UInputDeviceSubsystem* InputDeviceSubsystem = UInputDeviceSubsystem::Get())
		{
			InputDeviceSubsystem->RemoveDevicePropertyHandles(DevicePropertyHandles);
			DevicePropertyHandles.Empty();
		}
	}
}

/**
 * 当此武器的拥有Pawn死亡时的回调。
 * @param OwningActor 拥有Actor
 */
void ULyraWeaponInstance::OnDeathStarted(AActor* OwningActor)
{
	// 死亡时移除任何可能活动的设备属性，确保没有残留
	RemoveDeviceProperties();
}